#pragma once
#include <iostream>
#include <sys/time.h>
#include "Sock.hpp"
#include "log.hpp"

#define BITS 8
#define NUM (sizeof(fd_set) * BITS)
#define FD_NORM -1

class TcpServer
{
public:
    TcpServer(const uint16_t &port = 8080)
        : _port(port)
    {
        // 创建套接字
        _listensock = Sock::Socket();
        // 绑定
        Sock::Bind(_listensock, port);
        // 监听
        Sock::Listen(_listensock);

        logMessage(DEBUG, "create bask socke success.");

        // 初始化读事件数组
        for (int i = 0; i < NUM; ++i)
        {
            _rfds_array[i] = FD_NORM;
        }
        _rfds_array[0] = _listensock;
    }

    ~TcpServer()
    {
        if (_listensock)
            close(_listensock);
    }

    void Start()
    {
        while (true)
        {
            DebugPrint();
            //  每次都要重置一下事件集
            fd_set rfds;
            FD_ZERO(&rfds);

            int maxfd = _listensock;
            for (int i = 0; i < NUM; ++i)
            {

                if (_rfds_array[i] == FD_NORM)
                    continue;
                FD_SET(_rfds_array[i], &rfds);
                if (maxfd < _rfds_array[i])
                    maxfd = _rfds_array[i];
            }

            // 使用select进程多路复用
            // int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
            int ret = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (ret)
            {
            case 0:
                logMessage(NORMAL, "time out, please try again.");
                break;
            case -1:
                logMessage(WARNING, "select() error: %d, %s", errno, strerror(errno));
                break;
            default:
                logMessage(NORMAL, "select suceess, get a new link event.");
                HandlerEvent(rfds);
                break;
            }
        }
    }

private:
    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < NUM; ++i)
        {
            if (_rfds_array[i] == FD_NORM)
                continue;
            if (FD_ISSET(_rfds_array[i], &rfds))
            {
                if (_rfds_array[i] == _listensock)
                    Accepter();
                else
                    Recever(i);
            }
        }
    }

    void Accepter()
    {
        std::string clientip;
        uint16_t clientport;
        int serversock = Sock::Accept(_listensock, &clientip, &clientport);
        logMessage(NORMAL, "get a new link success.[%s:%d], sock is %d", clientip.c_str(), clientport, serversock);

        int pos = 1;
        for (; pos < NUM; ++pos)
        {
            if (_rfds_array[pos] == FD_NORM)
            {
                _rfds_array[pos] = serversock;
                break;
            }
        }
        if (pos == NUM)
        {
            logMessage(WARNING, "select server already full. colse: %d", serversock);
            close(serversock);
        }
    }

    void Recever(int pos)
    {
        logMessage(NORMAL, "message in, get IO event, sock is %d,", _rfds_array[pos]);

        char recvbuffer[1024];
        int ret = recv(_rfds_array[pos], recvbuffer, sizeof(recvbuffer) - 1, 0);
        if (ret > 0)
        {
            recvbuffer[ret] = 0;
            logMessage(DEBUG, "client[%d]# %s", _rfds_array[pos], recvbuffer);
        }
        else if (ret == 0)
        {
            logMessage(NORMAL, "client[%d] quit, server will close its sock.", _rfds_array[pos]);
            close(_rfds_array[pos]);
            _rfds_array[pos] = FD_NORM;
        }
        else
        {
            logMessage(WARNING, "%d sock recv error, %d, %s", errno, strerror(errno));
            close(_rfds_array[pos]);
            _rfds_array[pos] = FD_NORM;
        }
    }

    void DebugPrint()
    {
        std::cout << "_rfds_arry[]: ";
        for (int i = 0; i < NUM; ++i)
        {
            if (_rfds_array[i] != FD_NORM)
                std::cout << _rfds_array[i] << " ";
        }
        std::cout << std::endl;
    }

private:
    uint16_t _port;
    int _listensock;
    int _rfds_array[NUM];
};